﻿#region Copyrights Notice
//©2010 Microsoft Corporation. All rights reserved.
#endregion

#region usings
using System;
using System.Configuration;
using Microsoft.Web.Administration;

using Microsoft.InformationSecurity.CISF.Common.Utilities;
using System.Text.RegularExpressions;
using System.Xml;
using System.IO;

using System.Security.Principal;
using System.Threading;
using Microsoft.InformationSecurity.CISF.Security.Principal;
using Microsoft.InformationSecurity.CISF.Security.AuthZServices;
using CISFAppDeployer.AuthZServiceReference;
#endregion

#region Namespace
namespace CISFAppDeployer
{
    public class AppDeployer
    {
        #region Global Variables

        //regex
        private const string regexForDomainUser = @"^([a-z][a-z0-9.-]+)\\(?![\x20.]+$)([^\\/\x22[\]:|<>+=;,?*@]+)$";
        private const string regexForLocalUser = @"^(?![\x20.]+$)([^\\/\x22[\]:|<>+=;,?*@]+)$";

        //Config data
        static string webServerName;//iis server name
        static string applicationSourceFolderDirectrory;//folder where the bits of the app to be deployed reside
        static string applicationsDirectory;//site root folder + "applications"
        static string portalRootFolderName;//site root folder. derived from site name
        static string portalArchiveFolderName;//applications archive folder.
        static string applicationName;//name of the applcaition
        static string applicationSourceFolderName;//folder name of the source code of application
        //IIS metadata
        static string portalWebSiteName;//site name of the portal
        static string applicationPoolName;//
        static bool IsCustomAppPoolAccount;
        
        static string strAppPoolActName;
        static string strAppPoolActPassword;

        static IISAdmin iisAdmin;
        #endregion

        #region Main

        public static int Main(string[] args)
        {
            try
            {
                //Validate: check count of input arguments supplied
                if ((args.Length < 1) || (args.Length > 4))
                {
                    PrintAurgumentError("Argument Error: Invalid number of arguments.");
                    return 1; //return error code
                }

                //Validate: check the argument values and validate
                if (1 == ValidateArguments(args))
                {
                    return 1; //return error code
                }

                //Continue...
                bool installFlag = false;
                
                //loading configuration values from config XML file
                if (!LoadConfiguration(args[1]))
                {
                    return 1;
                }
                else
                {
                    //check deployer options
                    if (0 == string.Compare(args[0], "/i", StringComparison.InvariantCultureIgnoreCase)) // install
                    {
                        installFlag = true;
                    }
                    else if (0 == string.Compare(args[0], "/u", StringComparison.InvariantCultureIgnoreCase)) // for uninstall
                    {
                        installFlag = false;
                    }

                    //check if the custom app pool account flag is set to true
                    if (IsCustomAppPoolAccount == true)
                    {
                        if (args.Length < 4)
                        {
                            PrintAurgumentError("CustomAppPoolAccount is set to TRUE in config file but either UserName or Password is not provided in arguments.");
                            return 1;
                        }
                    }

                    //validate the config values
                    ValidateConfigurationParameters(installFlag);
                }
                //continue loading...
                //processing the argument values
                if (IsCustomAppPoolAccount == true)
                {
                    strAppPoolActName = args[2].ToString();
                    strAppPoolActPassword = args[3].ToString();
                }

                //now authorize users
                //Calling AuthZ service to authorize user
                if (!AuthorizeUser())
                {
                    return 1;
                }

                //Continue and call deployer for register/de-register activities
                return ApplicationDeploymentSteps(installFlag);
                
            }
            catch
            {
                return 1;
            }
        }

        #endregion

        #region Configuration Section

        public static bool LoadConfiguration(string configFile)
        {
            try
            {
                //from user suplied config
                ConfigXmlDocument configDoc = new ConfigXmlDocument();
                configDoc.Load(configFile);

                XmlNode node = configDoc.DocumentElement.SelectSingleNode("Data");

                //reading portal settings
                XmlNode key = node.SelectSingleNode("IISServerName");
                webServerName = key.Attributes.GetNamedItem("value").Value;

                key = node.SelectSingleNode("PortalWebsiteName");
                portalWebSiteName = key.Attributes.GetNamedItem("value").Value;

                key = node.SelectSingleNode("PortalRootFolder");
                portalRootFolderName = key.Attributes.GetNamedItem("value").Value;
                applicationsDirectory = portalRootFolderName + "Applications\\";

                key = node.SelectSingleNode("PortalArchiveFolder");
                portalArchiveFolderName = key.Attributes.GetNamedItem("value").Value;

                //reading app settings
                key = node.SelectSingleNode("ApplicationName");
                applicationName = key.Attributes.GetNamedItem("value").Value;

                key = node.SelectSingleNode("ApplicationFolderName");
                applicationSourceFolderName = key.Attributes.GetNamedItem("value").Value;

                key = node.SelectSingleNode("ApplicationSourceFolder");
                applicationSourceFolderDirectrory = key.Attributes.GetNamedItem("value").Value;                

                ////apppool and website settings
                key = node.SelectSingleNode("ApplicationPoolName");
                applicationPoolName = key.Attributes.GetNamedItem("value").Value;

                key = node.SelectSingleNode("CustomApplicationPoolAccount");
                string customAcc = key.Attributes.GetNamedItem("value").Value;
                if (string.Compare("true", customAcc, StringComparison.InvariantCultureIgnoreCase) == 0)
                    IsCustomAppPoolAccount = true;

                //finally
                return true;
            }
            catch
            {
                PrintError("Error loading configuration file");
                return false;
            }
        }

        #endregion

        #region Deployer Steps
        public static int ApplicationDeploymentSteps(bool installFlag)
        {
            try
            {
                //Starting Application Deployer
                if (installFlag)
                {
                    Logger.WriteLine("******** Starting Application Provisioning ********", "Information");
                }
                else
                {
                    Logger.WriteLine("******** Starting Application De-Provisioning ********", "Information");
                }

                //Setting up the application destination folder                
                if (installFlag)
                {
                    //*************************************
                    //Step1 : Managing folders and content
                    //*************************************

                    Logger.WriteLine(":: Managing folders and content...", "Information");
                    
                    //Creating new app folder in Portal applications directory                   
                    string strDestFolder = applicationsDirectory + applicationSourceFolderName;
                    if (!Installer.AddAppDirectory(applicationsDirectory, applicationSourceFolderName))
                    {
                        return 1; //return error code
                    }

                    Logger.WriteLine("    Copying files from source to destination...", "");

                    //Copying files from source to destination
                    if (!Installer.CopyAppDirectory(applicationSourceFolderDirectrory, strDestFolder, true))
                    {
                        return 1; //return error code
                    }

                    //*************************************
                    //Step2: Updating settings in the new application files (web.config and sitemap files)
                    //*************************************
                    Logger.WriteLine(":: Updating application settings...", "Information");
                    
                    // Updates Web.Config and Sitemap files as per the Portal deployment requirements
                    if (!Installer.UpdateNewAppSettings(applicationName, applicationSourceFolderName, strDestFolder))
                    {
                        return 1; //return error code
                    }
                }

                //*************************************
                //Step3 : IIS Administration
                //*************************************

                Logger.WriteLine(":: IIS Administration...", "Information");
                
                if (installFlag)
                {                    
                    string applicationPhysicalPath = iisAdmin.GetPortalRoot() + "\\Applications\\" + applicationSourceFolderName;
                    //Creating AppPool in the IIS
                    iisAdmin.CreateApplicationPool(applicationPoolName, IsCustomAppPoolAccount, strAppPoolActName, strAppPoolActPassword);

                    //Creating virtual application in IIS
                    if (!iisAdmin.AddApplication(@"/Applications/" + applicationSourceFolderName, applicationPoolName, applicationPhysicalPath))
                    {
                        return 1;
                    }
                }
                else
                {
                    //Removing the application from the IIS asnd also the applicaiton pool if it is not stared
                    if (!iisAdmin.RemoveApplication(@"/Applications/" + applicationSourceFolderName))
                    {
                        return 1;
                    }
                }


                //*************************************
                //Step4 : Final Step in Deployment: Provisioning the Application in the Portal
                //*************************************

                Logger.WriteLine(":: Updating Portal and rest of applications sitemap settings...", "Information");

                ////Updating the applications sitemaps
                //if (!iisAdmin.UpdateApplicationSitemaps(applicationSourceFolderName, applicationsDirectory, installFlag))
                //{
                //    return 1; //return error code
                //}
                
                //updating Portals sitemap
                if (!Installer.UpdatePortalSitemap(portalRootFolderName, applicationSourceFolderName,installFlag))
                {
                    return 1; //return error code
                }

                //UNINSTALL Application: remove application contents
                if (!installFlag)
                {
                    Logger.WriteLine(":: Managing folders and content...", "Information");

                    if (!Installer.RemoveAppDirectory(applicationsDirectory,applicationSourceFolderName, portalArchiveFolderName))
                    {
                        return 1; //return error code
                    }
                }

                //Ending Application Deployer
                if (installFlag)
                {
                    Logger.WriteLine("******** Completed Application Provisioning ********", "Information");
                }
                else
                {
                    Logger.WriteLine("******** Completed Application De-Provisioning ********", "Information");
                }

                //*********************************
                //Finally if all steps are complete... return true
                iisAdmin.Dispose();  
                return 0;//sucess

            }
            catch (Exception ex)
            {
                Logger.WriteLine(ex.Message.ToString(), "Error");
                Logger.WriteLine("Exiting Deployer....", "Error");
                return 1;
            }
        }
        #endregion

        #region Helper functions

        #region Validation
        private static int ValidateArguments(string[] args)
        {
            try
            {
                //Check: first paramenter must have "u" OR "i" options 
                if ((0 != string.Compare(args[0], "/i", StringComparison.InvariantCultureIgnoreCase)) &&
                    (0 != string.Compare(args[0], "/u", StringComparison.InvariantCultureIgnoreCase)))
                {
                    PrintAurgumentError("Invalid Option: First parameter should be /i or /u");
                    return 1;
                }

                //now doing username/password validation
                if (args.Length > 3)
                {
                    //check if the user name passed in is valid. 
                    //Note: AD validation for username and credentials at a later time
                    if (false == IsValidUser(args[2]))
                    {
                        PrintAurgumentError("Invalid UserName.");
                        return 1;
                    }
                }
            }
            catch (ArgumentNullException ex)
            {
                PrintAurgumentError("Failed to validate arguments" + ex.Message.ToString());
                return 1;
            }

            //return sucess
            return 0;
        }

        private static bool IsValidUser(string username)
        {
            try
            {
                Regex domainUserRegex = new Regex(regexForDomainUser, RegexOptions.IgnorePatternWhitespace);
                Regex localUserRegex = new Regex(regexForLocalUser, RegexOptions.IgnorePatternWhitespace);
                //check if the user is an proper local or domainuser
                if ((true == domainUserRegex.IsMatch(username)) || (true == localUserRegex.IsMatch(username)))
                    return true;
            }
            catch (ArgumentOutOfRangeException argOutOfRange)
            {
                Console.WriteLine("Out of range exception in IsValidUserDomainName " + argOutOfRange.Message + " \n" );
            }
            catch (ArgumentNullException argNull)
            {
                Console.WriteLine("Null exception in IsValidUserDomainName " + argNull.Message + " \n");
            }
            catch (ArgumentException argEx)
            {
                Console.WriteLine("Argument exception in IsValidUserDomainName " + argEx.Message + " \n");
            }
            return false;
        }

        private static void ValidateConfigurationParameters(bool isInstall)
        {
            Logger.WriteLine("******** Validating Configuration Values ********", "Information");

            iisAdmin = new IISAdmin(webServerName, portalWebSiteName);
            iisAdmin.IsValidSiteName();

            //Check if the source code folder exists
            DirectoryInfo dInfo = new DirectoryInfo(applicationSourceFolderDirectrory);
            if (!dInfo.Exists)
            {
                Logger.WriteLine("Source folder name entered in config file does not exist.", "Error");
                throw new ArgumentException("applicationSourceFolderDirectrory", "Source folder does not exist");
            }
            dInfo = new DirectoryInfo(portalRootFolderName);
            if (!dInfo.Exists)
            {
                Logger.WriteLine("The share name entered in config file does not exist.", "Error");
                throw new ArgumentException("portalRootFolderName", "Share folder does not exist");
            }

            if (string.IsNullOrEmpty(applicationSourceFolderName))
            {
                Logger.WriteLine("Invalid application folder name entered in config file.", "Error");
                throw new ArgumentException("applicationSourceFolderName", "Invalid folder name");
            }

            if (string.IsNullOrEmpty(applicationName))
            {
                Logger.WriteLine("Invalid application name entered in config file.", "Error");
                throw new ArgumentException("applicationName", "Invalid application name");
            }

            //Setting up ApplicationPool
            if (string.IsNullOrEmpty(applicationPoolName))
            {
                applicationPoolName = "DefaultAppPool";
            }

            //check if the application si already existing in the IIS
            if ((true == isInstall) && iisAdmin.IsApplicationExisting(applicationSourceFolderName))
            {
                //if folder already exists... do a clean exit and print appropriate message.
                Logger.WriteLine("Application already exists on server. De-register the application before attempting to install.", "Error");

                throw new ArgumentException("applicationName", "Application is already installed");
            }

        }

        #endregion

        #region Authorization
        private static bool AuthorizeUser()
        {
            try
            {
                Logger.WriteLine("******** User Authorization in Progress ********", "Information");

                //creating principal object
                CISFPrincipal principal = GetPrincipal();
                if (principal.CISFIdentity != null)
                {
                    if (principal.IsInRole("ApplicationInstaller"))
                    {
                        Logger.WriteLine("::Authorization Sucessful!", "Information");
                        return true;
                    }
                    else
                    {
                        Logger.WriteLine("::Access Denied!", "Error");
                        return false;
                    }
                }
                else
                {
                    Logger.WriteLine("AuthZ Error: An identity object was not created due to invalid IIdentity argument.", "Error");
                    return false;
                }
            }
            catch
            {
                return false;
            }
        }

        #region GetUserInfo
        /// <summary>
        /// todo
        /// </summary>
        /// <returns></returns>
        private static User GetUserInfo(string app, string name)
        {
            AuthZServiceClient client = new AuthZServiceClient();
            client.ClientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Delegation;
            User user = client.GetUserInformation(app, name);

            if (user == null)
                return null;

            return user;
        }

        #endregion

        #region GetPrincipal
        private static CISFPrincipal GetPrincipal()
        {
            //getting user identity
            IIdentity identity;
            identity = WindowsIdentity.GetCurrent();

            //application name as registered with CISF AuthZ sytem
            String app = "CISF Web Platform";

            if (String.IsNullOrEmpty(app))
                throw new Exception("Application name is null or empty.");

            Microsoft.InformationSecurity.CISF.Security.AuthZServices.User user = GetUserInfo(app, identity.Name);
            return new CISFPrincipal(identity, user);
        }
        #endregion

        #endregion

        #region Print methods
        public static void PrintAurgumentError(string text)
        {
            Logger.WriteLine(text, "Error");
            Logger.WriteLine("Please enter valid parameters and try again.", "Error");
            Logger.WriteLine("PortalAppDeployer.EXE /Option \"CONFIG FILE PATH\" \"USERNAME\" \"PASSWORD\" (Option = i or u)", "Warning");
        }
        public static void PrintError(string text)
        {
            Logger.WriteLine(text,"Error");
        }
        #endregion

        #endregion

    }
}
#endregion
